package ar.fi.uba.celdas.ambiente.gui;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.*;

import java.awt.Point;
import java.nio.FloatBuffer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.glu.Cylinder;
import org.lwjgl.util.glu.Disk;


import ar.fi.uba.celdas.ambiente.ILaberinto;
import ar.fi.uba.celdas.ambiente.ILaberinto.TIPO_CUBO;
import ar.fi.uba.celdas.ambiente.modelo.IRobot;

public class VistaLaberinto extends HWACanvas {

	private static final Log log = LogFactory.getLog(VistaLaberinto.class);
	
	private static final long serialVersionUID = 1L;
	private final static float LADO_CUBO = .5f;
	private final static float LADO_CUBO_2 = LADO_CUBO / 2f;
	private final static float RADIO_ROBOT = .24f;
	private final static float ALTO_ROBOT = LADO_CUBO / 5f;
	private static final float Z_TOP_OFFSET = .001f;
		
	private ILaberinto laberinto;
	private RobotInterpolado robot;	
		
	private float zoom = 2.0f;
	
	private int listLaberinto;
	private int listRobot;
	
	FloatBuffer lightPos = BufferUtils.createFloatBuffer(4).put(
			new float[] { 0f, 0f, 0f, 1f });	
	FloatBuffer lightDiffuseColor0 = BufferUtils.createFloatBuffer(4).put(
			new float[] { 2.5f, 2.5f, 2.5f, 2.5f });
	FloatBuffer lightAmbientColor0 = BufferUtils.createFloatBuffer(4).put(
			new float[] { .3f, .3f, .3f, 3f });
	
	FloatBuffer lightDir = BufferUtils.createFloatBuffer(4).put(
			new float[] { 0f, 0f, 1f, 1f });
	FloatBuffer lightDiffuseColor1 = BufferUtils.createFloatBuffer(4).put(
			new float[] { 0f, 0f, 1f, 1f });
	FloatBuffer lightAmbientColor1 = BufferUtils.createFloatBuffer(4).put(
			new float[] { 0f, 0f, 0f, 1f });
	private boolean laberintoModificado = false;
	
	public VistaLaberinto(ILaberinto laberinto, IRobot robot) {
		this.laberinto = laberinto;
		this.robot = new RobotInterpolado(robot, 20);
	}
	
	@Override
	protected synchronized void redraw() {
		if (laberintoModificado){
			doSetLaberinto();
		}
		
		robot.mover();
		float xRobot = (float) (robot.getPosicion().x * LADO_CUBO);
		float yRobot = (float) (robot.getPosicion().y * LADO_CUBO);
		
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glEnable(GL_CULL_FACE);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		
		//seteo la camara
		procesarInputs();
		
		gluLookAt(xRobot, yRobot, zoom, xRobot, yRobot, 0, 0, 1, 0);

		//actualizo luz en posicion del robot
		moverLuces(xRobot, yRobot, robot.getVectorDireccion());
		
		//dibujo robot
		glPushMatrix();			
			glTranslated( xRobot, yRobot, 0);
			glRotated(Math.toDegrees(robot.getRotacion()), 0, 0, 1);
			glCallList(listRobot);
		glPopMatrix();
		
		//dibujo laberinto
		glCallList(listLaberinto);		
	}

	private void moverLuces(float xRobot, float yRobot, FloatPoint direccion) {
		lightPos.rewind();
		lightPos.put(new float[] { xRobot, yRobot, LADO_CUBO_2 , 1f });
		lightPos.flip();		
		glLight(GL_LIGHT0, GL_POSITION, lightPos);
		
		lightPos.rewind();
		glLight(GL_LIGHT1, GL_POSITION, lightPos);
		lightDir.rewind();
		lightDir.put(new float[] { (float)direccion.x, (float)direccion.y, 0 , 1f });
		lightDir.flip();
		glLight(GL_LIGHT1, GL_SPOT_DIRECTION, lightDir);
		
		
	}

	private void procesarInputs() {
		//zoom
		if (Keyboard.isKeyDown(Keyboard.KEY_DOWN))
			zoom += 0.1;
		else if (Keyboard.isKeyDown(Keyboard.KEY_UP))
			zoom -= 0.1;
		
		//velocidad
		if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)){
			robot.setPasos(robot.getPasos() + 1);
		}
		else if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)){			
			if (robot.getPasos() > 1)
				robot.setPasos(robot.getPasos() - 1);
		}
	}

	private void dibujarRobot() {
		Cylinder cylinder = new Cylinder();
		Disk disk = new Disk();
		
		glColor3d(.3, .3, .3);
		cylinder.draw(RADIO_ROBOT, RADIO_ROBOT, ALTO_ROBOT, 30, 1);
		glTranslated(0, 0, ALTO_ROBOT);
		disk.draw(0, RADIO_ROBOT, 30, 1);
		
		//dibujo flecha sobre robot
		glColor3d(.6, .6, .6);
		glBegin(GL_TRIANGLES);
			glVertex3d(-.1, .1, Z_TOP_OFFSET);
			glVertex3d(.1, .1, Z_TOP_OFFSET);
			glVertex3d(0, .2, Z_TOP_OFFSET);
		glEnd();
	}

	private void dibujarCuboEnergia() {
		glColor3d(.8, .8, .8);
		glBegin(GL_QUADS);
			glNormal3f(0, 0, 1);
			glVertex3f(-LADO_CUBO_2, -LADO_CUBO_2, 0);
			glVertex3f(LADO_CUBO_2, -LADO_CUBO_2, 0);
			glVertex3f(LADO_CUBO_2, LADO_CUBO_2, 0);
			glVertex3f(-LADO_CUBO_2, LADO_CUBO_2, 0);
		glEnd();
	}

	private boolean hayObstaculo(int posX, int posY) {
		final Point tamanio = laberinto.getTamanio();
		if (posX >= 0 && posY >=0 && posX < tamanio.x && posY < tamanio.y
				&& laberinto.getCubo(posX, posY) == TIPO_CUBO.OBSTACULO)
			return true;
		else 
			return false;
			
	}
	
	private void dibujarCuboObstaculo(int x, int y) {
		boolean panelNorte = !hayObstaculo(x, y+1);
		boolean panelOeste = !hayObstaculo(x-1, y);
		boolean panelSur = !hayObstaculo(x, y-1);
		boolean panelEste = !hayObstaculo(x+1, y);
		
		glColor3d(.8, .2, .1);
		
		//cara este
		if (panelEste){
			glBegin(GL_QUADS);
				glNormal3f(1, 0, 0);
				glVertex3f(LADO_CUBO_2, -LADO_CUBO_2, -LADO_CUBO_2);
				glVertex3f(LADO_CUBO_2, LADO_CUBO_2, -LADO_CUBO_2);
				glVertex3f(LADO_CUBO_2, LADO_CUBO_2, LADO_CUBO_2);
				glVertex3f(LADO_CUBO_2, -LADO_CUBO_2, LADO_CUBO_2);
			glEnd();
		}
		//cara Oeste
		if (panelOeste){
			glBegin(GL_QUADS);
				glNormal3f(-1, 0, 0);
				glVertex3f(-LADO_CUBO_2, -LADO_CUBO_2, -LADO_CUBO_2);
				glVertex3f(-LADO_CUBO_2, -LADO_CUBO_2, LADO_CUBO_2);
				glVertex3f(-LADO_CUBO_2, LADO_CUBO_2, LADO_CUBO_2);
				glVertex3f(-LADO_CUBO_2, LADO_CUBO_2, -LADO_CUBO_2);
			glEnd();
		}
		//cara norte
		if (panelNorte){
			glBegin(GL_QUADS);
				//cara y frente
				glNormal3f(0, 1, 0);
				glVertex3f(-LADO_CUBO_2, LADO_CUBO_2, LADO_CUBO_2);
				glVertex3f(LADO_CUBO_2, LADO_CUBO_2, LADO_CUBO_2);
				glVertex3f(LADO_CUBO_2, LADO_CUBO_2, -LADO_CUBO_2);
				glVertex3f(-LADO_CUBO_2, LADO_CUBO_2, -LADO_CUBO_2);
			glEnd();
		}
		
		//cara sur
		if (panelSur){
			glBegin(GL_QUADS);
				//cara y fondo
				glNormal3f(0, -1, 0);
				glVertex3f(LADO_CUBO_2, -LADO_CUBO_2, -LADO_CUBO_2);
				glVertex3f(LADO_CUBO_2, -LADO_CUBO_2, LADO_CUBO_2);
				glVertex3f(-LADO_CUBO_2, -LADO_CUBO_2, LADO_CUBO_2);
				glVertex3f(-LADO_CUBO_2, -LADO_CUBO_2, -LADO_CUBO_2);								
			glEnd();
		}
		glBegin(GL_QUADS);
		//cara superior		
		glNormal3f(0, 0, 1);
			glVertex3f(-LADO_CUBO_2, -LADO_CUBO_2, LADO_CUBO_2);
			glVertex3f(LADO_CUBO_2, -LADO_CUBO_2, LADO_CUBO_2);
			glVertex3f(LADO_CUBO_2, LADO_CUBO_2, LADO_CUBO_2);
			glVertex3f(-LADO_CUBO_2, LADO_CUBO_2, LADO_CUBO_2);
		glEnd();
	}

	private void dibujarCuboLibre(int x, int y) {
		//color especial si es punto de salida y entrada
		if (laberinto.getEntrada().equals(new Point(x, y)))
			glColor3d(1, 1, 8);
		else if (laberinto.getSalida().equals(new Point(x, y)))
			glColor3d(1, 8, 1);
		else 
			glColor3d(.6, .5, .5);			
		
		glBegin(GL_QUADS);
			glNormal3f(0, 0, 1);
			glVertex3f(-LADO_CUBO_2, -LADO_CUBO_2, 0);
			glVertex3f(LADO_CUBO_2, -LADO_CUBO_2, 0);
			glVertex3f(LADO_CUBO_2, LADO_CUBO_2, 0);
			glVertex3f(-LADO_CUBO_2, LADO_CUBO_2, 0);
		glEnd();
	}

	@Override
	protected void initGL() {
		log.debug("LWJGL: " + Sys.getVersion() + " / " + LWJGLUtil.getPlatformName());
		log.debug("GL_VENDOR: " + glGetString(GL_VENDOR));
		log.debug("GL_RENDERER: " + glGetString(GL_RENDERER));
		log.debug("GL_VERSION: " + glGetString(GL_VERSION));				
		
		glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
		glEnable(GL_COLOR_MATERIAL);
		glEnable(GL_DEPTH_TEST);
		
		lightPos.flip();
		lightDir.flip();
		lightDiffuseColor0.flip();
		lightAmbientColor0.flip();
		lightDiffuseColor1.flip();
		lightAmbientColor1.flip();
		
		//spot en la frente del robot
		glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 25f);
		glLight(GL_LIGHT1, GL_SPOT_DIRECTION, lightDir);
		glLight(GL_LIGHT1, GL_POSITION, lightPos);		
		glLight(GL_LIGHT1, GL_DIFFUSE, lightDiffuseColor1);		
		glLight(GL_LIGHT1, GL_AMBIENT, lightAmbientColor1);
		glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 2f);
		//glEnable(GL_LIGHT1);
		
		lightPos.rewind();
		//luz radial en robot
		glLight(GL_LIGHT0, GL_POSITION, lightPos);		
		glLight(GL_LIGHT0, GL_DIFFUSE, lightDiffuseColor0);		
		glLight(GL_LIGHT0, GL_AMBIENT, lightAmbientColor0);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, .5f);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1f);
		glEnable(GL_LIGHT0);
		
		glShadeModel(GL_BLEND);
		//glShadeModel(GL_NEAREST);
		glEnable(GL_LIGHTING);

		
		//genero laberinto
		listLaberinto = glGenLists(1);
		glNewList(listLaberinto, GL_COMPILE);			
		generarLaberinto();
		glEndList();
		
		//genero robot;
		listRobot = glGenLists(1);
		glNewList(listRobot, GL_COMPILE);
			dibujarRobot();
		glEndList();
	}

	private void generarLaberinto() {			
			for (int x = 0; x < laberinto.getTamanio().getX(); ++x)
				for (int y = 0; y < laberinto.getTamanio().getY(); ++y){
					ILaberinto.TIPO_CUBO tipo = laberinto.getCubo(x, y);
					glPushMatrix();
						glTranslated(x * LADO_CUBO, y * LADO_CUBO, 0);
						if (tipo == TIPO_CUBO.LIBRE){
							dibujarCuboLibre(x, y);
						}
						else if (tipo == TIPO_CUBO.OBSTACULO){
							glTranslated(0, 0, LADO_CUBO_2);
							dibujarCuboObstaculo(x, y);							
						}
						else if (tipo == TIPO_CUBO.ENERGIA){
							dibujarCuboEnergia();
						}
					glPopMatrix();
				}
	}
		
	public synchronized void setLaberinto(ILaberinto laberinto) {
		this.laberinto = laberinto;
		this.laberintoModificado  = true;
	}
	
	public void doSetLaberinto(){
		//genero la displaylist
		glDeleteLists(listLaberinto, 1);
		listLaberinto = glGenLists(1);
		glNewList(listLaberinto, GL_COMPILE);			
		generarLaberinto();
		glEndList();
		laberintoModificado = false;

	}	
	
	public synchronized void setRobot(IRobot robot){
		this.robot = new RobotInterpolado(robot, 20);
	}
	
	@Override
	public void destroy() {		
		glDeleteLists(listRobot, 1);
		glDeleteLists(listLaberinto, 1);
		super.destroy();		
	}

}
